/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include "scripting.h"
#include "strings.h"
#include "convert.h"


template<typename T>
HRESULT fillIntArray( IDispatch* a, List<T>& list )
{
    HRESULT hr = S_OK;

    IDispatch* disp = a;

    DISPID dispId = 0;
    LPOLESTR lpPush[] = { L"push" };

    hr = disp->GetIDsOfNames( IID_NULL, lpPush, 1, LOCALE_USER_DEFAULT, &dispId );
    if ( FAILED( hr ) )
    {
        return S_FALSE;
    }

    DISPID namedArgs[] = { 0 };
    VARIANT* values = new VARIANT[ list.count() ];
    ::memset( values, 0, list.count() * sizeof( VARIANT ) );
    VARIANT varResult = { 0 };

    int j = list.count();
    for( auto& i : list )
    {
        VARIANT& item = values[ --j ];
        item.vt = VT_INT;
        item.intVal = ( int ) i;
    }
    DISPPARAMS p = { values, namedArgs, list.count(), 1 };

    hr = disp->Invoke( dispId, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &p, NULL, NULL, NULL );
    if ( FAILED( hr ) )
        return S_FALSE;

    delete values;

    return hr;
}

HRESULT pushArray( IDispatch* a, VARIANT& value )
{
    HRESULT hr = S_OK;

    IDispatch* disp = a;

    DISPID dispId = 0;
    LPOLESTR lpPush[] = { L"push" };

    hr = disp->GetIDsOfNames( IID_NULL, lpPush, 1, LOCALE_USER_DEFAULT, &dispId );
    if ( FAILED( hr ) )
    {
        return S_FALSE;
    }

    VARIANT& rgvarg = value;
    DISPPARAMS p = { &rgvarg, NULL, 1, 0 };

    hr = disp->Invoke( dispId, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &p, NULL, NULL, NULL );

    return hr;
}

template<typename T>
HRESULT pushNumericArray( IDispatch* a, enum VARENUM vt, T value )
{
    VARIANT rgvarg = { 0 };

    switch ( vt )
    {
    case VT_I4:
    case VT_INT:
        rgvarg.vt = VT_INT;
        rgvarg.intVal = ( int ) value;
        break;
    case VT_UI4:
    case VT_UINT:
        rgvarg.vt = VT_UINT;
        rgvarg.intVal = ( uint_t ) value;
        break;
    case VT_R4:
    case VT_R8:
        rgvarg.vt = VT_R8;
        rgvarg.dblVal = ( double ) value;
        break;
    }

    return ::pushArray( a, rgvarg );
}

IDispatch* listToArray( IWebBrowser2* browser, List<byte_t>& list )
{
    IDispatch* disp = NULL;
    HRESULT hr = ::newArray( JsArray, browser, &disp );

    hr = ::fillIntArray( disp, list );

    return disp;
}

HRESULT newArray( JsArrayType arrayType, IWebBrowser2* wb, IDispatch** a, int length )
{
    HRESULT hr = S_OK;

    CComPtr<IDispatch> dispDocument;
    hr = wb->get_Document( &dispDocument );
    if ( FAILED( hr ) )
        return hr;
    ATLASSERT( dispDocument );

    CComQIPtr<IHTMLDocument2> htmlDocument( dispDocument );
    if ( !htmlDocument )
        return E_NOINTERFACE;

    CComPtr<IHTMLWindow2> htmlWindow;
    hr = htmlDocument->get_parentWindow( &htmlWindow );
    if ( FAILED( hr ) )
        return hr;
    ATLASSERT( htmlWindow );

    IDispatch* dispScript = NULL; // window dispatch
    hr = htmlWindow->QueryInterface( IID_IDispatch, ( void** ) &dispScript );
    if ( FAILED( hr ) )
        return hr;
    ATLASSERT( dispScript );

    // get DISPID for "Array"
    DISPID dispId = 0;
    LPOLESTR jsArray = OLESTR("Array"),
        jsUint8Array = OLESTR("Uint8Array"), 
        jsInt8Array = OLESTR("Int8Array");
    LPOLESTR jsArrayClassName = NULL;
    switch ( arrayType )
    {
    case JsArray: jsArrayClassName = jsArray; break;
    case JsUint8Array: jsArrayClassName = jsUint8Array; break;
    case JsInt8Array: jsArrayClassName = jsInt8Array; break;
    }
    LPOLESTR lpNames[] = { jsArrayClassName };
    hr = dispScript->GetIDsOfNames( IID_NULL, lpNames, 1, LOCALE_USER_DEFAULT, &dispId );
    if ( FAILED( hr ) )
        return hr;

    // invoke script dispatch with DISPATCH_PROPERTYGET for "Array"
    CComVariant vRet;
    DISPPARAMS noParams = { 0 };
    CComVariant vResult;
    hr = dispScript->Invoke( dispId, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &noParams, &vResult, NULL, NULL );
    if ( FAILED( hr ) )
        return hr;

    // check result: should be a VT_DISPATCH
    if ( ( vResult.vt != VT_DISPATCH ) || ( vResult.pdispVal == NULL ) )
        return DISP_E_TYPEMISMATCH;

    // get IDispatchEx on returned IDispatch
    CComQIPtr<IDispatchEx> prototype( vResult.pdispVal );
    if ( !prototype )
        return E_NOINTERFACE;

    // call InvokeEx with DISPID_VALUE and DISPATCH_CONSTRUCT to construct new array
    VARIANT vResult2 = { 0 };
    VARIANTARG vLengthArg = { 0 };
    vLengthArg.vt = VT_INT;
    vLengthArg.intVal = length;
    DISPPARAMS constructorLenghtParam = { &vLengthArg, NULL, 1, 0 };
    DISPPARAMS* constructorParams = length > 0 ? &constructorLenghtParam : &noParams;

    hr = prototype->InvokeEx( DISPID_VALUE, LOCALE_USER_DEFAULT, DISPATCH_CONSTRUCT, constructorParams, &vResult2, NULL, NULL );
    if ( FAILED( hr ) )
        return hr;

    // vResult should contain the new array now.
    if ( ( vResult2.vt != VT_DISPATCH ) || ( vResult2.pdispVal == NULL ) )
        return DISP_E_TYPEMISMATCH;

    // get IDispatchEx on returned IDispatch
    //CComQIPtr<IDispatchEx> theObject( vResult.pdispVal );
    *a = vResult2.pdispVal;

    // add values by invoking InvokeEx
    /*CString sName;
    for( size_t n = 0; n < data.GetCount(); n++ )
    {
    sName.Format( _T("%i"), n );

    hr = theObject->GetDispID( CComBSTR( sName ), fdexNameEnsure, &dispId );

    if ( FAILED( hr ) )
    break;
    DISPID namedArgs[] = { DISPID_PROPERTYPUT };
    DISPPARAMS p = { &data[ n ], NULL, 1, 0 };

    hr = theObject->InvokeEx( dispId, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &p, NULL, NULL, NULL );

    if ( FAILED( hr ) )
    break;
    }*/

    return S_OK;
}

bool variantToArray( const VARIANT* pArray, List<VARIANT>*& pItems )
{
    pItems = NULL;

    // convert variant to dispatch object
    IDispatch* pDispatch = pArray->pdispVal;
    if (!pDispatch)
        return false;

    // get DISPID of length parameter from array object
    LPOLESTR stringLength = L"length";
    DISPID dispidLength = 0;

    HRESULT hr = pDispatch->GetIDsOfNames( IID_NULL, &stringLength, 1, LOCALE_USER_DEFAULT, &dispidLength );
    if ( FAILED ( hr ) )
        return false;

    // get the number of elements using the DISPID of length parameter
    VARIANT vLengthValue;
    DISPPARAMS dispParams = { 0 };

    hr = pDispatch->Invoke( dispidLength, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispParams, &vLengthValue, NULL, NULL );
    if ( FAILED( hr ) )
        return false;

    int length = 0; // length of the array
    if ( !::varAsNumeric( &vLengthValue, length ) )
        return false;

    List<VARIANT>* items = new List<VARIANT>( length );

    char buffer[ 16 ] = { 0 };

    // get items of array
    for (int i = 0 ; i < length ; ++i )
    {
        ::memset( buffer, 0, 16 );

        // get DISPID of item[i] from array object
        char* indexA = Convert::ToString( buffer, i );//::_itoa( i, buffer, 10 );
        wchar_t* indexW = String::ToWchar( indexA );

        DISPID dispidIndex = NULL;

        hr = pDispatch->GetIDsOfNames( IID_NULL, &indexW, 1, LOCALE_USER_DEFAULT, &dispidIndex );
        if ( FAILED( hr ) )
            continue;

        VARIANT vItem = { 0 };

        hr = pDispatch->Invoke( dispidIndex, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispParams, &vItem, NULL, NULL );
        if ( FAILED( hr ) )
            continue;

        if ( !( ::varIsNumeric( &vItem ) || ::varIsString( &vItem ) ) )
        {
            delete items;

            return false;
        }

        //memcpy( &items[ i ], &vItem, sizeof( VARIANT ) );
        items->add( vItem );
    }

    pItems = items;

    return true;
}

char* bstrToChars( BSTR string )
{
    if ( !string ) return NULL;

    char* result = NULL;
    UINT length = ::SysStringLen( string );

    String::ToUtf8( &result, string, length );

    return result;
}

BSTR charToBstr( const char* cstring )
{
    if ( cstring == NULL )
        return NULL;

    wchar_t* pwchar = String::ToWchar( cstring );

    BSTR bstr = ::SysAllocString( pwchar );

    return bstr;
}
